{
 "metadata": {
  "kernelspec": {
   "language": "python",
   "display_name": "Python 3",
   "name": "python3"
  },
  "language_info": {
   "name": "python",
   "version": "3.10.13",
   "mimetype": "text/x-python",
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "pygments_lexer": "ipython3",
   "nbconvert_exporter": "python",
   "file_extension": ".py"
  },
  "kaggle": {
   "accelerator": "gpu",
   "dataSources": [
    {
     "sourceId": 8640523,
     "sourceType": "datasetVersion",
     "datasetId": 5174680
    }
   ],
   "dockerImageVersionId": 30733,
   "isInternetEnabled": false,
   "language": "python",
   "sourceType": "notebook",
   "isGpuEnabled": true
  }
 },
 "nbformat_minor": 4,
 "nbformat": 4,
 "cells": [
  {
   "cell_type": "code",
   "source": "import os\nimport time\nimport torch\nimport torch.backends.cudnn as cudnn\nimport torch.nn as nn\nfrom torch.utils.data import DataLoader\nimport torch.nn.functional as F\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pickle\nimport torchvision.transforms as transforms\nimport torchvision.datasets as datasets\n\n# 定义学习率、动量、权重衰减等超参数\nlr = 0.01\nmomentum = 0.9\nweight_decay = 1e-4\nstart_epoch = 0\nepochs = 100\n\n# 根据是否有CUDA设备，选择合适的设备\ndevice = torch.device(\"cuda:0\" if torch.cuda.is_available() else \"cpu\")\n\n# z-pool池化层，用于结合最大值和平均值\nclass ZPool(nn.Module):\n    def forward(self, x):\n        return torch.cat((torch.max(x, 1)[0].unsqueeze(1), torch.mean(x, 1).unsqueeze(1)), dim=1)\n\n\n# 注意力模块\nclass SpatialGate(nn.Module):\n    def __init__(self):\n        super(SpatialGate, self).__init__()\n        # 先Z-Pool池化\n        self.z_pool = ZPool()\n        # 再卷积 k*k的卷积核卷积->归一化处理\n        self.conv = nn.Sequential(\n            nn.Conv2d(in_channels=2, out_channels=1, kernel_size=7, stride=1, padding=3),\n            nn.BatchNorm2d(1)\n        )\n        # 通过sigmoid来生成的注意力权值\n        self.sigmoid = nn.Sigmoid()\n\n    def forward(self, x):\n        z_pool_out = self.z_pool(x)\n        out = self.conv(z_pool_out)\n        return x * self.sigmoid(out)\n\n# 用于实现三元组注意力机制\nclass TripletAttention(nn.Module):\n    def __init__(self, spatial=True):\n        super(TripletAttention, self).__init__()\n        self.spatial = spatial\n\n        # 对三个分支进行初始化\n        # 通道C和空间W维度交互\n        self.cw = SpatialGate()\n        # 通道C和空间H维度交互\n        self.ch = SpatialGate()\n        # 空间H和空间W进行注意力计算\n        if self.spatial:\n            self.hw = SpatialGate()\n\n    def forward(self, x):\n        # 0 2 1 3    b w c h\n        x_perm1 = x.permute(0, 2, 1, 3).contiguous()\n        x_out1 = self.ch(x_perm1)\n        # 0 2 1 3    b c w h\n        x_out1 = x_out1.permute(0, 2, 1, 3).contiguous()\n\n        # 0 3 2 1    b h w c\n        x_perm2 = x.permute(0, 3, 2, 1).contiguous()\n        x_out2 = self.cw(x_perm2)\n        # 0 3 2 1    b c w h\n        x_out2 = x_out2.permute(0, 3, 2, 1).contiguous()\n\n        if self.spatial:\n            x_out3 = self.hw(x)\n            return (1 / 3) * (x_out1 + x_out2 + x_out3)\n        else:\n            return (1 / 2) * (x_out1 + x_out2)\n\n\n# SE模块\nclass SEModule(nn.Module):\n    def __init__(self, channels, reduction=16):\n        super(SEModule, self).__init__()\n        self.avg_pool = nn.AdaptiveAvgPool2d(1)\n        self.fc1 = nn.Conv2d(channels, channels // reduction, kernel_size=1, padding=0)\n        self.relu = nn.ReLU(inplace=True)\n        self.fc2 = nn.Conv2d(channels // reduction, channels, kernel_size=1, padding=0)\n        self.sigmoid = nn.Sigmoid()\n\n    def forward(self, input):\n        x = self.avg_pool(input)\n        x = self.fc1(x)\n        x = self.relu(x)\n        x = self.fc2(x)\n        x = self.sigmoid(x)\n        return input * x\n\n\n# 残差层\nclass Bottleneck(nn.Module):\n    # 输出通道数为输入通道数的4倍\n    expansion = 4\n\n    # 后续需要为其添加新参数\n    def __init__(self, inplanes, planes, stride=1, downsample=None, scales=4, groups=1, is_first_block=0,\n                 activation='gelu'):\n        super(Bottleneck, self).__init__()\n\n        self.downsample = downsample\n        self.scales = scales\n        self.groups = groups\n        self.stride = stride\n        self.is_first_block = is_first_block\n\n        # 激活函数\n        if activation == 'relu':\n            self.activation = nn.ReLU(inplace=True)\n        elif activation == 'celu':\n            self.activation = nn.CELU(inplace=True)\n        elif activation == 'gelu':\n            self.activation = nn.GELU()\n        elif activation == 'elu':\n            self.activation = nn.ELU(inplace=True)\n        elif activation == 'leaky-relu':\n            self.activation = nn.LeakyReLU(inplace=True)\n        else:\n            raise NotImplementedError(f\"Activation not implemented!\")\n\n        outplanes = groups * planes\n        # 第一个卷积层：卷积核尺寸： 1×1 ，填充值为 0， 步长为 1\n        self.conv1 = nn.Conv2d(in_channels=inplanes, out_channels=outplanes, kernel_size=1, stride=1, bias=False)\n        self.bn1 = nn.BatchNorm2d(outplanes)\n\n        # 第二个卷积结构：卷积核尺寸： 3×3 ，填充值为 1， 步长为 1\n        self.conv2 = nn.ModuleList([nn.Conv2d(outplanes // scales, outplanes // scales,\n                                              kernel_size=3, stride=stride, padding=1, groups=groups, bias=False) for _\n                                    in\n                                    range(scales - 1)])\n        self.bn2 = nn.ModuleList([nn.BatchNorm2d(outplanes // scales) for _ in range(scales - 1)])\n\n        # 第三个卷积层：卷积核尺寸： 1×1 ，填充值为 0， 步长为 1\n        self.conv3 = nn.Conv2d(outplanes, planes * self.expansion, kernel_size=1, stride=1, bias=False)\n        self.bn3 = nn.BatchNorm2d(planes * self.expansion)\n        \n        # 处理第一个块\n        if is_first_block == 1:\n            self.pool = nn.AvgPool2d(kernel_size=3, stride=stride, padding=1)\n\n        # SE模块\n        self.se = SEModule(planes * self.expansion)\n        self.semodule = SEModule(outplanes // scales)\n        # TA模块\n        self.ta = TripletAttention()\n\n    def forward(self, x):\n        identity = x  # 将原始输入暂存为shortcut的输出\n        # 对下采样进行处理\n        # 如果Indentity block就是直连；如果Conv2 Block就需要对残差边就行卷积，改变通道数和size\n        if self.downsample is not None:\n            identity = self.downsample(identity)\n\n        # 1*1卷积层\n        out = self.conv1(x)\n        out = self.bn1(out)\n        out = self.activation(out)\n        \n        # 3*3卷积结构\n        x_scales = torch.chunk(out, self.scales, 1)\n        for i in range(self.scales - 1):\n            if i == 0 or self.is_first_block == 1:\n                y_scale = x_scales[i]\n            else:\n                y_scale = y_scale + x_scales[i]\n            y_scale = self.conv2[i](y_scale)\n            y_scale = self.activation(self.bn2[i](y_scale))\n            y_scale = self.semodule(y_scale)\n            y_scale = self.ta(y_scale)\n            if i == 0:\n                out = y_scale\n            else:\n                out = torch.cat((out, y_scale), 1)\n        if self.scales != 1 and self.is_first_block == 0:\n            out = torch.cat((out, x_scales[self.scales - 1]), 1)\n        elif self.scales != 1 and self.is_first_block == 1:\n            out = torch.cat((out, self.pool(x_scales[self.scales - 1])), 1)\n\n        # 1*1卷积层\n        out = self.conv3(out)\n        out = self.bn3(out)\n\n        # 是否加入SE模块--该结构利用系数scale来使网络自适应的减弱或增强该通道的特征，与注意力机制有异曲同工之妙。\n        out = self.se(out)\n        \n        # 添加triplet_attention\n        out = self.ta(out)\n\n        # 残差连接 out=F(X)+X\n        out += identity\n        \n        self.activation(out)\n\n        return out\n\n\nclass Res2Net(nn.Module):\n    def __init__(self, block, layers, num_classes=1000, scales=4, groups=1):\n        super(Res2Net, self).__init__()\n        \n        # 通道数初始化\n        self.inplanes = 64\n\n        # 起始：3*3的卷积层，3*3的最大池化层\n        self.conv1 = nn.Sequential(\n             nn.Conv2d(3, self.inplanes, kernel_size=3, stride=1, padding=1, bias=False),\n             nn.BatchNorm2d(self.inplanes),\n             nn.ReLU(inplace=True),\n             nn.Conv2d(self.inplanes, self.inplanes, kernel_size=3, stride=1, padding=1, bias=False),\n             nn.BatchNorm2d(self.inplanes),\n             nn.ReLU(inplace=True),\n             nn.Conv2d(self.inplanes, self.inplanes, kernel_size=3, stride=1, padding=1, bias=False),\n             nn.BatchNorm2d(self.inplanes),\n             nn.ReLU(inplace=True)\n             )\n\n        self.bn1 = nn.BatchNorm2d(self.inplanes)\n        self.relu = nn.ReLU(inplace=True)\n        self.maxpool = nn.MaxPool2d(kernel_size=3, stride=1, padding=1)\n\n        # 残差结构\n        self.layer1 = self._make_layer(block, 64, layers[0], stride=1, scales=scales, groups=groups)\n        self.layer2 = self._make_layer(block, 128, layers[1], stride=2, scales=scales, groups=groups)\n        self.layer3 = self._make_layer(block, 256, layers[2], stride=2, scales=scales, groups=groups)\n        self.layer4 = self._make_layer(block, 512, layers[3], stride=2, scales=scales, groups=groups)\n\n        # 平均池化+全连接层\n        self.avgpool = nn.AdaptiveAvgPool2d((1, 1))\n        self.fc = nn.Linear(512 * block.expansion, num_classes)\n\n        # 权重初始化\n        for m in self.modules():\n            if isinstance(m, nn.Conv2d):\n                nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')\n            elif isinstance(m, nn.BatchNorm2d):\n                nn.init.constant_(m.weight, 1)\n                nn.init.constant_(m.bias, 0)\n\n    def _make_layer(self, block, planes, layer, stride=1, scales=4, groups=1, se=True):\n        # 积步长不为1或深度扩张有变化，导致F(X)与X的shape不同的残差块，就要对X定义下采样函数，使之shape相同\n        downsample = None\n        if stride != 1 or self.inplanes != planes * block.expansion:\n            downsample = nn.Sequential(\n                nn.Conv2d(self.inplanes, planes * block.expansion, kernel_size=1, stride=stride, bias=False),\n                nn.BatchNorm2d(planes * block.expansion),\n            )\n\n        layers = []\n        # 第一个残差块需要下采样\n        layers.append(block(self.inplanes, planes, stride=stride, downsample=downsample,\n                            scales=scales, groups=groups, is_first_block=1))\n        self.inplanes = planes * block.expansion\n\n        # 通过循环堆叠其余残差块\n        for i in range(1, layer):\n            layers.append(block(self.inplanes, planes, scales=scales, groups=groups))\n\n        return nn.Sequential(*layers)\n\n    def forward(self, x):\n        x = self.conv1(x)\n        x = self.bn1(x)\n        x = self.relu(x)\n        x = self.maxpool(x)\n\n        x = self.layer1(x)\n        x = self.layer2(x)\n        x = self.layer3(x)\n        x = self.layer4(x)\n\n        x = self.avgpool(x)\n        x = torch.flatten(x, 1)\n        x = self.fc(x)\n\n        return x\n\n\ndef res2net50(num_classes=100, scales=4, groups=1):\n    return Res2Net(Bottleneck, [3, 4, 6, 3], num_classes, scales, groups)\n\n\ndef res2net101(num_classes=100, scales=4, groups=1):\n    return Res2Net(Bottleneck, [3, 4, 23, 3], num_classes, scales, groups)\n\n\nif __name__ == \"__main__\":\n    transform_train = transforms.Compose([\n        transforms.RandomCrop(32, padding=4),\n        transforms.RandomHorizontalFlip(),\n        transforms.RandomVerticalFlip(),  # 随机垂直翻转\n        transforms.RandomRotation(10),  # 随机旋转\n        transforms.ToTensor(),\n        transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)),\n    ])\n    transform_test = transforms.Compose([\n        transforms.ToTensor(),\n        transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)),\n    ])\n    train_set = datasets.CIFAR100('/kaggle/input/mydata', train=True,\n                                  download=False, transform=transform_train)\n    test_set = datasets.CIFAR100('/kaggle/input/mydata', train=False,\n                                 download=False, transform=transform_test)\n    train_loader = torch.utils.data.DataLoader(train_set, batch_size=128,\n                                               shuffle=True, num_workers=4)\n    val_loader = torch.utils.data.DataLoader(test_set, batch_size=128,\n                                             shuffle=False, num_workers=4)\n    global model\n    model = res2net101()\n    model.to(device)\n    #损失函数\n    loss_fn=nn.CrossEntropyLoss() \n    loss_fn.to(device)\n    # define loss function (criterion) and optimizer\n    criterion = nn.CrossEntropyLoss().to(device)\n    optimizer = torch.optim.SGD(\n        model.parameters(),\n        lr,\n        momentum=momentum,\n        weight_decay=weight_decay,\n    )\n    cudnn.benchmark = True\n    train_acc_list = []\n    train_loss_list = []\n    test_acc_list = []\n    test_loss_list=[]\n    for epoch in range(start_epoch, epochs):\n        print(\"-----第{}轮训练开始------\".format(epoch + 1))\n        train_loss=0.0\n        test_loss=0.0\n        train_sum,train_cor,test_sum,test_cor=0,0,0,0\n \n        #训练步骤开始\n        model.train()\n        for batch_idx,(data,target) in enumerate(train_loader):\n            data,target=data.to(device),target.to(device)\n \n            optimizer.zero_grad()  # 要将梯度清零，因为如果梯度不清零，pytorch中会将上次计算的梯度和本次计算的梯度累加\n            output = model(data)\n            loss = loss_fn(output, target)\n            loss.backward()\n            optimizer.step()  # 更新所有的参数\n \n            # 计算每轮训练集的Loss\n            train_loss += loss.item()\n \n            _, predicted = torch.max(output.data, 1)  # 选择最大的（概率）值所在的列数就是他所对应的类别数，\n            train_cor += (predicted == target).sum().item()  # 正确分类个数\n            train_sum += target.size(0)  # train_sum+=predicted.shape[0]\n \n        #测试步骤开始\n        model.eval()\n        with torch.no_grad():\n            for batch_idx1,(data,target) in enumerate(val_loader):\n                data, target = data.to(device), target.to(device)\n \n                output = model(data)\n                loss = loss_fn(output, target)\n                test_loss+=loss.item()\n                _, predicted = torch.max(output.data, 1)\n                test_cor += (predicted == target).sum().item()\n                test_sum += target.size(0)\n     \n        print(\"Train loss:{}   Train accuracy:{}%   Test loss:{}   Test accuracy:{}%\".format(train_loss/batch_idx,100*train_cor/train_sum,\n                                                                                       test_loss/batch_idx1,100*test_cor/test_sum))\n        train_loss_list.append(train_loss / batch_idx)\n        train_acc_list.append(100 * train_cor / train_sum)\n        test_acc_list.append(100 * test_cor/ test_sum)\n        test_loss_list.append(test_loss / batch_idx1)\n \n    #保存网络\n    torch.save(model,\"/kaggle/working/CIFAR100_epoch{}.pth\".format(epochs))\n",
   "metadata": {
    "_uuid": "8f2839f25d086af736a60e9eeb907d3b93b6e0e5",
    "_cell_guid": "b1076dfc-b9ad-4769-8c92-a6c4dae69d19",
    "execution": {
     "iopub.status.busy": "2024-06-09T14:33:04.228464Z",
     "iopub.execute_input": "2024-06-09T14:33:04.228900Z"
    },
    "trusted": true
   },
   "execution_count": null,
   "outputs": [
    {
     "name": "stdout",
     "text": "-----第1轮训练开始------\nTrain loss:3.774345403451186   Train accuracy:13.07%   Test loss:3.4509579279483895   Test accuracy:18.33%\n-----第2轮训练开始------\nTrain loss:3.1386805014732557   Train accuracy:23.068%   Test loss:3.043814766101348   Test accuracy:25.18%\n-----第3轮训练开始------\nTrain loss:2.7835510266132846   Train accuracy:29.614%   Test loss:2.6506492694218955   Test accuracy:32.73%\n-----第4轮训练开始------\nTrain loss:2.5263151633433805   Train accuracy:34.348%   Test loss:2.501855348929381   Test accuracy:35.58%\n-----第5轮训练开始------\nTrain loss:2.312532667013315   Train accuracy:38.856%   Test loss:2.3003385112835812   Test accuracy:39.68%\n-----第6轮训练开始------\nTrain loss:2.148190404818608   Train accuracy:42.43%   Test loss:2.1754267704792514   Test accuracy:42.64%\n-----第7轮训练开始------\nTrain loss:2.002099972504836   Train accuracy:45.93%   Test loss:2.1050817645513096   Test accuracy:44.92%\n-----第8轮训练开始------\nTrain loss:1.8737241906997486   Train accuracy:48.688%   Test loss:2.072523731451768   Test accuracy:44.72%\n-----第9轮训练开始------\nTrain loss:1.7636311628879644   Train accuracy:51.224%   Test loss:1.9914204218448737   Test accuracy:47.01%\n-----第10轮训练开始------\nTrain loss:1.6683626862672658   Train accuracy:53.33%   Test loss:1.9399037193029354   Test accuracy:48.01%\n-----第11轮训练开始------\nTrain loss:1.5662975671963815   Train accuracy:55.808%   Test loss:1.8520563901999059   Test accuracy:49.68%\n-----第12轮训练开始------\nTrain loss:1.487191532819699   Train accuracy:57.664%   Test loss:1.8839703492629223   Test accuracy:49.63%\n-----第13轮训练开始------\nTrain loss:1.4000707439887219   Train accuracy:59.822%   Test loss:1.8769327860612135   Test accuracy:49.26%\n-----第14轮训练开始------\nTrain loss:1.3170098893153361   Train accuracy:61.796%   Test loss:1.842611890572768   Test accuracy:50.79%\n-----第15轮训练开始------\nTrain loss:1.2437098350280371   Train accuracy:63.768%   Test loss:1.830139962526468   Test accuracy:51.03%\n-----第16轮训练开始------\nTrain loss:1.1672959526379902   Train accuracy:65.764%   Test loss:1.7688441490515685   Test accuracy:52.09%\n-----第17轮训练开始------\nTrain loss:1.0897113324739993   Train accuracy:67.628%   Test loss:1.787805743706532   Test accuracy:52.12%\n-----第18轮训练开始------\nTrain loss:1.0267583938745353   Train accuracy:69.17%   Test loss:1.7867022294264574   Test accuracy:52.8%\n-----第19轮训练开始------\nTrain loss:0.9492051797035413   Train accuracy:71.254%   Test loss:1.785494524698991   Test accuracy:52.93%\n-----第20轮训练开始------\nTrain loss:0.8930459010295378   Train accuracy:72.876%   Test loss:1.8098804690898993   Test accuracy:52.49%\n-----第21轮训练开始------\nTrain loss:0.8327982199497712   Train accuracy:74.514%   Test loss:1.8244030659015362   Test accuracy:52.9%\n-----第22轮训练开始------\nTrain loss:0.7605829999997066   Train accuracy:76.326%   Test loss:1.8268843904519692   Test accuracy:52.61%\n-----第23轮训练开始------\nTrain loss:0.7022808626676217   Train accuracy:78.166%   Test loss:1.8463474023036468   Test accuracy:53.52%\n-----第24轮训练开始------\nTrain loss:0.6522212292903509   Train accuracy:79.402%   Test loss:1.912331414528382   Test accuracy:52.54%\n-----第25轮训练开始------\nTrain loss:0.5978131126898986   Train accuracy:81.184%   Test loss:1.970542617333241   Test accuracy:52.43%\n-----第26轮训练开始------\nTrain loss:0.5377312967410455   Train accuracy:82.88%   Test loss:1.9544243354063768   Test accuracy:53.23%\n-----第27轮训练开始------\nTrain loss:0.492444809507101   Train accuracy:84.11%   Test loss:1.916006549810752   Test accuracy:53.95%\n-----第28轮训练开始------\nTrain loss:0.45996325959761936   Train accuracy:85.408%   Test loss:1.8927937150001526   Test accuracy:55.11%\n-----第29轮训练开始------\nTrain loss:0.41819516065029   Train accuracy:86.518%   Test loss:1.9995408623646467   Test accuracy:54.65%\n-----第30轮训练开始------\nTrain loss:0.3815872589747111   Train accuracy:87.692%   Test loss:1.9000144608509846   Test accuracy:55.3%\n-----第31轮训练开始------\nTrain loss:0.33685612758764855   Train accuracy:88.956%   Test loss:2.0157412458688784   Test accuracy:55.18%\n-----第32轮训练开始------\nTrain loss:0.3248911046064817   Train accuracy:89.438%   Test loss:2.1143092864598985   Test accuracy:53.65%\n-----第33轮训练开始------\nTrain loss:0.29658763328423865   Train accuracy:90.44%   Test loss:2.191034110692831   Test accuracy:53.7%\n-----第34轮训练开始------\nTrain loss:0.28105866996905743   Train accuracy:90.794%   Test loss:2.1653942465782166   Test accuracy:53.51%\n-----第35轮训练开始------\nTrain loss:0.24469667184047209   Train accuracy:92.008%   Test loss:2.125147298360482   Test accuracy:55.22%\n-----第36轮训练开始------\nTrain loss:0.23382959835804426   Train accuracy:92.32%   Test loss:2.137083982809996   Test accuracy:55.43%\n-----第37轮训练开始------\nTrain loss:0.21560412660623207   Train accuracy:92.964%   Test loss:2.1664672111853576   Test accuracy:55.2%\n-----第38轮训练开始------\nTrain loss:0.20237090236101396   Train accuracy:93.392%   Test loss:2.1320149103800454   Test accuracy:56.52%\n-----第39轮训练开始------\nTrain loss:0.1815457077171558   Train accuracy:94.16%   Test loss:2.1945770306464953   Test accuracy:55.54%\n-----第40轮训练开始------\n",
     "output_type": "stream"
    }
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "# 加载模型\n",
    "model = torch.load(\"/kaggle/working/CIFAR100_epoch50.pth\")\n",
    "model = model.to(device)\n",
    "\n",
    "# 测试步骤开始\n",
    "model.eval()\n",
    "correct = 0\n",
    "total = 0\n",
    "with torch.no_grad():\n",
    "    for data in val_loader:\n",
    "        images, labels = data\n",
    "        images, labels = images.to(device), labels.to(device)\n",
    "        outputs = model(images)\n",
    "        _, predicted = torch.max(outputs.data, 1)\n",
    "        total += labels.size(0)\n",
    "        correct += (predicted == labels).sum().item()\n",
    "\n",
    "print('Accuracy of the network on the test images: %d %%' % (\n",
    "        100 * correct / total))"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "\n",
    "# 创建一个新的图像\n",
    "plt.figure()\n",
    "\n",
    "# 绘制训练精度\n",
    "plt.plot(range(epochs), train_acc_list, label='Train Accuracy')\n",
    "\n",
    "# 绘制测试精度\n",
    "plt.plot(range(epochs), test_acc_list, label='Test Accuracy')\n",
    "\n",
    "# 添加图例\n",
    "plt.legend()\n",
    "\n",
    "# 添加标题和标签\n",
    "plt.title('Train and Test Accuracy')\n",
    "plt.xlabel('Epoch')\n",
    "plt.ylabel('Accuracy')\n",
    "\n",
    "# 显示图像\n",
    "plt.show()"
   ],
   "metadata": {
    "collapsed": false
   }
  }
 ]
}
